/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://dylab-modules.googlecode.com

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

//===========================================================================================
#include "dotT4Box_ModelStructureLoader.h"
#include <dylab/utils/property/PropertyTree.h>


//===========================================================================================
const dylab::String dylab::dotT4Box_ModelStructureLoader::COMPONENT_NAME = "t4box";

//===========================================================================================
dylab::dotT4Box_ModelStructureLoader::dotT4Box_ModelStructureLoader()
: ComModelStructureLoader(COMPONENT_NAME)
{
    // constructor is currently empty
}
//-----------------------------------------------------------------------------
void dylab::dotT4Box_ModelStructureLoader::loadFromPropertyTree(const PropertyTree & _pt)
{
    // retrieve the properties
    ArrayI32<3> node_count = ArrayI32<3>::parse(_pt.getNodeValue("nodeCount"));
    real_t      node_mass = _pt.getNodeValueT("nodeMass", DYLAB_R(0.1));
    Vector3     step = _pt.getNodeValueT("step", Vector3(32, 32, 32));
    BitFlags16  fixed = BitFlags16::parse(_pt.getNodeValue("fixed", "0 0 0 0 0 0"), 6);

    // build the structure
    _buildModel(node_count, node_mass, step, fixed);

    //
    //_cutModel_Sphere(getModelToLoad(), CUT_OUTER, DYLAB_R(32));

    // retrieve boundary mesh interface
    ComModelStructure::IBoundaryMesh * boundaryMesh = getModelToLoad()->getBoundaryMeshInterface();

    // generate the boundary mesh
    boundaryMesh->generateFromTetrahedralVolume();

    // set the uv-texture mapping for each boundary vertex
    for (ComModelStructure::IBoundaryMesh::VertexArray::Iterator vertex(boundaryMesh->vertexIterator()); vertex.isValid(); vertex++)
    {
        vertex->setTextureU(0);
        vertex->setTextureV(1);
    }
}
//-----------------------------------------------------------------------------
void dylab::dotT4Box_ModelStructureLoader::_buildModel(const ArrayI32<3> & _nodeCount, real_t _nodeMass, const Vector3 & _step, const BitFlags16 & _fixed)
{
    int32_t allNodeCount, cubeCount, i;
    int32_t xStart, yStart, zStart;

    // store the number of all box nodes and cube count (each cube is subdivided into 5 tetrahedrons)
    allNodeCount = _nodeCount[0] * _nodeCount[1] * _nodeCount[2];
    cubeCount = (_nodeCount[0] - 1) * (_nodeCount[1] - 1) * (_nodeCount[2] - 1);

    // compute position of the first node, which is located at the upper-left corner of the bottom box plane
    xStart = static_cast<int32_t> (- _step.x() * (_nodeCount[0] - 1) / 2);
    yStart = static_cast<int32_t> (- _step.y() * (_nodeCount[1] - 1) / 2);
    zStart = static_cast<int32_t> (- _step.z() * (_nodeCount[2] - 1) / 2);

    // retrieve mass points interface
    ComModelStructure::IMassPoints * massPoints = getModelToLoad()->getMassPointsInterface();
    ComModelStructure::IMassPoints::MassPoint * mp;

    //
    ComModelStructure::IMassPoints::MassPoint::id_t firstMP = massPoints->createMassPoints(allNodeCount);

    // first pass is going through all box nodes initializing its attributes based on supplied parameters
    for (int32_t y = 0; y < _nodeCount[1]; y++) 
        for (int32_t z = 0; z < _nodeCount[2]; z++) 
            for (int32_t x = 0; x < _nodeCount[0]; x++) 
    {
        // compute node index based on its position within the box
        i = x + z * _nodeCount[0] + y * (_nodeCount[2] * _nodeCount[0]);

        // create new mass point
        mp = massPoints->massPointReader().get(firstMP + i);

        // set mass point position in the box
        mp->currState().position = Vector3(xStart + _step.x() * x, yStart + _step.y() * y, zStart + _step.z() * z);

        // set mass point texture coordinates based on mass point position in terms of the box - TODO: implement better texture mapping
        // TODO
        //mp->setU(float(x + z * _nodeCount[0]) / float(_nodeCount[0] * _nodeCount[2])).setV(float(y) / float(_nodeCount[1] - 1));

        // set mass point mass, all nodes have the same mass
        mp->setMass(_nodeMass);

        // set boundary flag
        if ((x == 0) || (x == _nodeCount[0] - 1) || (y == 0) || (y == _nodeCount[1] - 1) || (z == 0) || (z == _nodeCount[2] - 1))
            mp->flags().set(ComModelStructure::IMassPoints::MassPoint::F_BOUNDARY);

        // set mass point flag representing, whether it is _fixed or not
        if (((x == 0) && _fixed.test(0)) || ((x == _nodeCount[0] - 1) && _fixed.test(1)) ||
            ((y == 0) && _fixed.test(2)) || ((y == _nodeCount[1] - 1) && _fixed.test(3)) ||
            ((z == 0) && _fixed.test(4)) || ((z == _nodeCount[2] - 1) && _fixed.test(5)))
            mp->flags().set(ComModelStructure::IMassPoints::MassPoint::F_FIXED);
    }

    // utility variables
    int32_t n, firstNode, xzNodes;
    xzNodes = _nodeCount[0] * _nodeCount[2];
    bool rotateCube = true;

    // retrieve tetrahedral volume interface
    ComModelStructure::ITetrahedralVolume * tetraVol = getModelToLoad()->getTetrahedralVolumeInterface();

    // second pass goes through indiviadual cubes creating 5 tetrahedrons for each one of them
    for (i = 0, n = 1; i < cubeCount; i++, n++)
    {
        if ((n % _nodeCount[0]) == 0) { // last node of the row => new row (z++)
            n++;
            if ((_nodeCount[0] % 2) == 1) // odd number of _nodeCount[0] => extra switch of the rotateCube flag when entering new row
                rotateCube = !rotateCube;
        }
        if (((n + _nodeCount[0] - 1) % xzNodes) == 0) // first node on the last row => new level (y++)
            n += _nodeCount[0];

        // index of the cube first node (upper-left node at the bottom plane)
        firstNode = n - 1;

        if (rotateCube) // this tetrahedron box will be rotated around axis y about 90 degrees
            tetraVol->generateCubeSection(firstNode, firstNode + 1, firstNode + 1 + _nodeCount[0], firstNode + _nodeCount[0], 
                            xzNodes + firstNode, xzNodes + firstNode + 1, xzNodes + firstNode + 1 + _nodeCount[0], xzNodes + firstNode + _nodeCount[0]);
        else
            tetraVol->generateCubeSection(firstNode + 1, firstNode + 1 + _nodeCount[0], firstNode + _nodeCount[0], firstNode,
                            xzNodes + firstNode + 1, xzNodes + firstNode + 1 + _nodeCount[0], xzNodes + firstNode + _nodeCount[0], xzNodes + firstNode);
 
        // swap the the cube rotation flag
        rotateCube = !rotateCube;
    }
}
//-----------------------------------------------------------------------------
void dylab::dotT4Box_ModelStructureLoader::_cutModel_Sphere(cut_t _cutType, real_t _radius)
{
    // retrieve tetrahedral volume interface
    ComModelStructure::ITetrahedralVolume * tetraVol = getModelToLoad()->getTetrahedralVolumeInterface();

    // retrieve mass points interface
    ComModelStructure::IMassPoints * massPoints = getModelToLoad()->getMassPointsInterface();   

    // walk through all tetrahedra
    for (ComModelStructure::ITetrahedralVolume::TetrahedronMap::Iterator tetra(tetraVol->tetrahedronIterator()); tetra.isValid(); )
    {
        // check all tetrahedron nodes, if their lie in the sphere
        bool remove = false;
        for (TetrahedronGeometry::VertexIdxIterator vertexIdx; vertexIdx.isValid(); vertexIdx++)
        {
            // test, if the node lies inside the sphere
            Vector3 p = tetra->getMassPoint(vertexIdx)->currState().position;
            if ((p.x() * p.x() + p.y() * p.y() + p.z() * p.z()) > _radius * _radius)            
                remove = true;
        }

        // at least one node lies outside the sphere - remove the tetrahedron
        if (remove)
        {
            ComModelStructure::ITetrahedralVolume::Tetrahedron::id_t tetraID = ~tetra;
            tetra++;
            tetraVol->destroyTetrahedron(tetraID);
        }
        else
        {
            tetra++;
        }
    }
}
//-----------------------------------------------------------------------------